package cc.siyecao.fastdfs.service.impl;

import cc.siyecao.fastdfs.command.storage.*;
import cc.siyecao.fastdfs.downloader.FileWriterDownloader;
import cc.siyecao.fastdfs.downloader.StreamDownloader;
import cc.siyecao.fastdfs.enums.MetadataFlag;
import cc.siyecao.fastdfs.extception.FastDfsException;
import cc.siyecao.fastdfs.model.FileInfo;
import cc.siyecao.fastdfs.model.Metadata;
import cc.siyecao.fastdfs.model.StorageInfo;
import cc.siyecao.fastdfs.model.StoreFile;
import cc.siyecao.fastdfs.protocol.ProtocolConstants;
import cc.siyecao.fastdfs.server.StorageServer;
import cc.siyecao.fastdfs.service.IStorageService;
import cc.siyecao.fastdfs.service.ITrackerService;
import cc.siyecao.fastdfs.uploader.BuffUploader;
import cc.siyecao.fastdfs.uploader.FileUploader;
import cc.siyecao.fastdfs.uploader.StreamUploader;
import cc.siyecao.fastdfs.util.Base64;
import cc.siyecao.fastdfs.util.BytesUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;

@Service
public class StorageServiceImpl implements IStorageService {
    @Autowired
    private ITrackerService trackerService;
    @Autowired
    private StorageServer storageServer;
    public final static Base64 base64 = new Base64( '-', '_', '.', 0 );

    @Override
    public StoreFile uploadFile(String groupName, File file, String fileExtName, Set<Metadata> metaList) {
        StorageInfo storageInfo = trackerService.getStoreStorage( groupName );
        FileUploader fileUploader = new FileUploader( file );
        UploadFileCommand command = new UploadFileCommand( (byte) storageInfo.getStorePathIndex(), groupName, fileExtName, file.length(), fileUploader, false );
        StoreFile storeFile = storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
        if (metaList != null && metaList.size() > 0) {
            setMetadata( storeFile.getGroupName(), storeFile.getFileName(), metaList );
        }
        return storeFile;
    }

    @Override
    public StoreFile uploadFile(String groupName, InputStream inputStream, long fileSize, String fileExtName, Set<Metadata> metaList) {
        StorageInfo storageInfo = trackerService.getStoreStorage( groupName );
        StreamUploader streamUploader = new StreamUploader( inputStream, fileSize );
        UploadFileCommand command = new UploadFileCommand( (byte) storageInfo.getStorePathIndex(), groupName, fileExtName, fileSize, streamUploader, false );
        StoreFile storeFile = storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
        if (metaList != null && metaList.size() > 0) {
            setMetadata( storeFile.getGroupName(), storeFile.getFileName(), metaList );
        }
        return storeFile;
    }

    @Override
    public StoreFile uploadFile(String groupName, byte[] fileBuff, int offset, int fileSize, String fileExtName, Set<Metadata> metaList) {
        StorageInfo storageInfo = trackerService.getStoreStorage( groupName );
        BuffUploader buffUploader = new BuffUploader( fileBuff, offset, fileSize );
        UploadFileCommand command = new UploadFileCommand( (byte) storageInfo.getStorePathIndex(), groupName, fileExtName, fileSize, buffUploader, false );
        StoreFile storeFile = storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
        if (metaList != null && metaList.size() > 0) {
            setMetadata( storeFile.getGroupName(), storeFile.getFileName(), metaList );
        }
        return storeFile;
    }

    @Override
    public StoreFile uploadSlaveFile(String groupName, String masterFileName, String prefixName, File file, String fileExtName, Set<Metadata> metaList) {
        if ((groupName == null || groupName.length() == 0) || (masterFileName == null || masterFileName.length() == 0) || (prefixName == null || prefixName.length() == 0)) {
            throw new FastDfsException( "uploadSlaveFile: groupName and masterFileName and prefixName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, masterFileName );
        FileUploader fileUploader = new FileUploader( file );
        UploadSlaveFileCommand command = new UploadSlaveFileCommand( file.length(), masterFileName, prefixName, fileExtName, fileUploader );
        StoreFile storeFile = storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
        if (metaList != null && metaList.size() > 0) {
            setMetadata( storeFile.getGroupName(), storeFile.getFileName(), metaList );
        }
        return storeFile;
    }

    @Override
    public StoreFile uploadSlaveFile(String groupName, String masterFileName, String prefixName, InputStream inputStream, long fileSize, String fileExtName, Set<Metadata> metaList) {
        if ((groupName == null || groupName.length() == 0) || (masterFileName == null || masterFileName.length() == 0) || (prefixName == null || prefixName.length() == 0)) {
            throw new FastDfsException( "uploadSlaveFile: groupName and masterFileName and prefixName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, masterFileName );
        StreamUploader fileUploader = new StreamUploader( inputStream, fileSize );
        UploadSlaveFileCommand command = new UploadSlaveFileCommand( fileSize, masterFileName, prefixName, fileExtName, fileUploader );
        StoreFile storeFile = storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
        if (metaList != null && metaList.size() > 0) {
            setMetadata( storeFile.getGroupName(), storeFile.getFileName(), metaList );
        }
        return storeFile;
    }

    @Override
    public StoreFile uploadSlaveFile(String groupName, String masterFileName, String prefixName, byte[] fileBuff, int offset, int fileSize, String fileExtName, Set<Metadata> metaList) {
        if ((groupName == null || groupName.length() == 0) || (masterFileName == null || masterFileName.length() == 0) || (prefixName == null || prefixName.length() == 0)) {
            throw new FastDfsException( "uploadSlaveFile: groupName and masterFileName and prefixName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, masterFileName );
        BuffUploader fileUploader = new BuffUploader( fileBuff, offset, fileSize );
        UploadSlaveFileCommand command = new UploadSlaveFileCommand( fileSize, masterFileName, prefixName, fileExtName, fileUploader );
        StoreFile storeFile = storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
        if (metaList != null && metaList.size() > 0) {
            setMetadata( storeFile.getGroupName(), storeFile.getFileName(), metaList );
        }
        return storeFile;
    }

    @Override
    public StoreFile uploadAppenderFile(String groupName, File file, String fileExtName, Set<Metadata> metaList) {
        StorageInfo storageInfo = trackerService.getStoreStorage( groupName );
        FileUploader fileUploader = new FileUploader( file );
        UploadFileCommand command = new UploadFileCommand( (byte) storageInfo.getStorePathIndex(), groupName, fileExtName, file.length(), fileUploader, true );
        StoreFile storeFile = storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
        if (metaList != null && metaList.size() > 0) {
            setMetadata( storeFile.getGroupName(), storeFile.getFileName(), metaList );
        }
        return storeFile;
    }

    @Override
    public StoreFile uploadAppenderFile(String groupName, InputStream inputStream, long fileSize, String fileExtName, Set<Metadata> metaList) {
        StorageInfo storageInfo = trackerService.getStoreStorage( groupName );
        StreamUploader fileUploader = new StreamUploader( inputStream, fileSize );
        UploadFileCommand command = new UploadFileCommand( (byte) storageInfo.getStorePathIndex(), groupName, fileExtName, fileSize, fileUploader, true );
        StoreFile storeFile = storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
        if (metaList != null && metaList.size() > 0) {
            setMetadata( storeFile.getGroupName(), storeFile.getFileName(), metaList );
        }
        return storeFile;
    }

    @Override
    public StoreFile uploadAppenderFile(String groupName, byte[] fileBuff, int offset, int fileSize, String fileExtName, Set<Metadata> metaList) {
        StorageInfo storageInfo = trackerService.getStoreStorage( groupName );
        BuffUploader fileUploader = new BuffUploader( fileBuff, offset, fileSize );
        UploadFileCommand command = new UploadFileCommand( (byte) storageInfo.getStorePathIndex(), groupName, fileExtName, fileSize, fileUploader, true );
        StoreFile storeFile = storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
        if (metaList != null && metaList.size() > 0) {
            setMetadata( storeFile.getGroupName(), storeFile.getFileName(), metaList );
        }
        return storeFile;
    }

    @Override
    public int appendFile(String groupName, String appenderFileName, File file) {
        if ((groupName == null || groupName.length() == 0) || (appenderFileName == null || appenderFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and appenderFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, appenderFileName );
        FileUploader fileUploader = new FileUploader( file );
        AppendFileCommand command = new AppendFileCommand( groupName, appenderFileName, file.length(), fileUploader );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public int appendFile(String groupName, String appenderFileName, InputStream inputStream, long fileSize) {
        if ((groupName == null || groupName.length() == 0) || (appenderFileName == null || appenderFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and appenderFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, appenderFileName );
        StreamUploader fileUploader = new StreamUploader( inputStream, fileSize );
        AppendFileCommand command = new AppendFileCommand( groupName, appenderFileName, fileSize, fileUploader );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public int appendFile(String groupName, String appenderFileName, byte[] fileBuff, int offset, int fileSize) {
        if ((groupName == null || groupName.length() == 0) || (appenderFileName == null || appenderFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and appenderFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, appenderFileName );
        BuffUploader fileUploader = new BuffUploader( fileBuff, offset, fileSize );
        AppendFileCommand command = new AppendFileCommand( groupName, appenderFileName, fileSize, fileUploader );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public int modifyFile(String groupName, String appenderFileName, File file, int fileOffset) {
        if ((groupName == null || groupName.length() == 0) || (appenderFileName == null || appenderFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and appenderFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, appenderFileName );
        FileUploader fileUploader = new FileUploader( file );
        ModifyCommand command = new ModifyCommand( groupName, appenderFileName, fileOffset, file.length(), fileUploader );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public int modifyFile(String groupName, String appenderFileName, InputStream inputStream, long fileSize, int fileOffset) {
        if ((groupName == null || groupName.length() == 0) || (appenderFileName == null || appenderFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and appenderFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, appenderFileName );
        StreamUploader fileUploader = new StreamUploader( inputStream, fileSize );
        ModifyCommand command = new ModifyCommand( groupName, appenderFileName, fileOffset, fileSize, fileUploader );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public int modifyFile(String groupName, String appenderFileName, byte[] fileBuff, int fileOffset, int bufferOffset, int bufferfileSize) {
        if ((groupName == null || groupName.length() == 0) || (appenderFileName == null || appenderFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and appenderFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, appenderFileName );
        BuffUploader fileUploader = new BuffUploader( fileBuff, bufferOffset, bufferfileSize );
        ModifyCommand command = new ModifyCommand( groupName, appenderFileName, fileOffset, bufferfileSize, fileUploader );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public StoreFile regenerateAppenderFileName(String groupName, String appenderFileName) {
        if ((groupName == null || groupName.length() == 0) || (appenderFileName == null || appenderFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and appenderFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, appenderFileName );
        FileNameCommand command = new FileNameCommand( groupName, appenderFileName );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public int deleteFile(String groupName, String remoteFileName) {
        if ((groupName == null || groupName.length() == 0) || (remoteFileName == null || remoteFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and remoteFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, remoteFileName );
        DeleteFileCommand command = new DeleteFileCommand( groupName, remoteFileName );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public int truncateFile(String groupName, String appenderFileName, long truncatedFileSize) {
        if ((groupName == null || groupName.length() == 0) || (appenderFileName == null || appenderFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and remoteFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, appenderFileName );
        TruncateCommand command = new TruncateCommand( groupName, appenderFileName, truncatedFileSize );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public byte[] downloadFile(String groupName, String remoteFileName, long fileOffset, long downloadBytes) {
        if ((groupName == null || groupName.length() == 0) || (remoteFileName == null || remoteFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and remoteFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getFetchStorage( groupName, remoteFileName );
        DownloadByteCommand command = new DownloadByteCommand( groupName, remoteFileName, fileOffset, downloadBytes );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public int downloadFile(String groupName, String remoteFileName, OutputStream out, long fileOffset, long downloadBytes) {
        if ((groupName == null || groupName.length() == 0) || (remoteFileName == null || remoteFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and remoteFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getFetchStorage( groupName, remoteFileName );
        StreamDownloader downloader = new StreamDownloader( out );
        DownloadCommand command = new DownloadCommand( groupName, remoteFileName, fileOffset, downloadBytes, downloader );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public int downloadFile(String groupName, String remoteFileName, File file, long fileOffset, long downloadBytes) {
        if ((groupName == null || groupName.length() == 0) || (remoteFileName == null || remoteFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and remoteFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getFetchStorage( groupName, remoteFileName );
        FileWriterDownloader downloader = new FileWriterDownloader( file );
        DownloadCommand command = new DownloadCommand( groupName, remoteFileName, fileOffset, downloadBytes, downloader );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public Set<Metadata> getMetadata(String groupName, String remoteFileName) {
        if ((groupName == null || groupName.length() == 0) || (remoteFileName == null || remoteFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and remoteFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getFetchStorage( groupName, remoteFileName );
        GetMetadataCommand command = new GetMetadataCommand( groupName, remoteFileName );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public int setMetadata(String groupName, String remoteFileName, Set<Metadata> metaList, MetadataFlag metadataFlag) {
        if ((groupName == null || groupName.length() == 0) || (remoteFileName == null || remoteFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and remoteFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getUpdateStorage( groupName, remoteFileName );
        SetMetadataCommand command = new SetMetadataCommand( groupName, remoteFileName, metaList, metadataFlag );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }

    @Override
    public FileInfo getFileInfo(String groupName, String remoteFileName) {
        if ((groupName == null || groupName.length() == 0) || (remoteFileName == null || remoteFileName.length() == 0)) {
            throw new FastDfsException( "getFileInfo: groupName and remoteFileName can not null!" );
        }
        if (remoteFileName.length() < ProtocolConstants.FDFS_FILE_PATH_LEN + ProtocolConstants.FDFS_FILENAME_BASE64_LENGTH + ProtocolConstants.FDFS_FILE_EXT_NAME_MAX_LEN + 1) {
            throw new FastDfsException( "getFileInfo: remoteFileName invalid!" );
        }
        byte[] buff = base64.decodeAuto( remoteFileName.substring( ProtocolConstants.FDFS_FILE_PATH_LEN, ProtocolConstants.FDFS_FILE_PATH_LEN + ProtocolConstants.FDFS_FILENAME_BASE64_LENGTH ) );
        short fileType;
        long fileSize = BytesUtil.buff2long( buff, 4 * 2 );
        if (((fileSize & ProtocolConstants.APPENDER_FILE_SIZE) != 0)) {
            fileType = FileInfo.FILE_TYPE_APPENDER;
        } else if ((remoteFileName.length() > ProtocolConstants.TRUNK_LOGIC_FILENAME_LENGTH) || ((remoteFileName.length() > ProtocolConstants.NORMAL_LOGIC_FILENAME_LENGTH) &&
                ((fileSize & ProtocolConstants.TRUNK_FILE_MARK_SIZE) == 0))) {
            fileType = FileInfo.FILE_TYPE_SLAVE;
        } else {
            fileType = FileInfo.FILE_TYPE_NORMAL;
        }

        if (fileType == FileInfo.FILE_TYPE_SLAVE || fileType == FileInfo.FILE_TYPE_APPENDER) { //slave file or appender file
            FileInfo fi = this.queryFileInfo( groupName, remoteFileName );
            if (fi == null) {
                return null;
            }
            fi.setFileType( fileType );
            return fi;
        }

        int createTimestamp = BytesUtil.buff2int( buff, 4 );
        if ((fileSize >> 63) != 0) {
            fileSize &= 0xFFFFFFFFL;  //low 32 bits is file size
        }
        int crc32 = BytesUtil.buff2int( buff, 4 * 4 );

        return new FileInfo( false, fileType, fileSize, createTimestamp, crc32, BytesUtil.buff2IpAddress( buff, 0 ) );
    }

    @Override
    public FileInfo queryFileInfo(String groupName, String remoteFileName) {
        if ((groupName == null || groupName.length() == 0) || (remoteFileName == null || remoteFileName.length() == 0)) {
            throw new FastDfsException( "getFileInfo: groupName and remoteFileName can not null!" );
        }
        if ((groupName == null || groupName.length() == 0) || (remoteFileName == null || remoteFileName.length() == 0)) {
            throw new FastDfsException( "appendFile: groupName and remoteFileName can not null!" );
        }
        StorageInfo storageInfo = trackerService.getFetchStorage( groupName, remoteFileName );
        QueryFileInfoCommand command = new QueryFileInfoCommand( groupName, remoteFileName );
        return storageServer.excuteCmd( storageInfo.getInetSocketAddress(), command );
    }
}
